package com.shopping.service.service.user.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
import com.shopping.common.core.constant.ErrorCodeEnum;
import com.shopping.common.core.constant.GloatDeletedFlagEnum;
import com.shopping.common.core.object.ResponseResult;
import com.shopping.common.core.object.TokenData;
import com.shopping.common.core.util.ExcelExportUtil;
import com.shopping.common.core.util.MyModelUtil;
import com.shopping.common.core.util.MyPageUtil;
import com.shopping.common.core.util.RedisKeyUtil;
import com.shopping.common.sp.dto.user.UserDto;
import com.shopping.common.sp.query.user.UserQuery;
import com.shopping.common.sp.vo.user.UserVo;
import com.shopping.service.mapper.UserMapper;
import com.shopping.common.sp.model.user.User;
import com.shopping.service.service.user.UserService;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author: zlw
 * @date: 2024/8/15
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedissonClient redissonClient;

    private static final int BATCH_SIZE = 10 * 10000; //每个线程处理的记录数
    private static final int THREAD_COUNT = 10;
    private static final int TOTAL_RECORDS = 10 * BATCH_SIZE;//总记录数,假设有100万条数据
    private final Object lock = new Object(); //用于文件写入的同步锁


    @Override
    public ResponseResult<Void> addUser(UserDto dto) {
        if(dto == null){
            return ResponseResult.error(ErrorCodeEnum.PARAMETER_CANNOT_BE_EMPTY);
        }
        User user = BeanUtil.copyProperties(dto, User.class);
        //赋值创建人
        Long loginUserId = TokenData.takeFormRequest().getUserId();
        String loginUserName = TokenData.takeFormRequest().getUserName();
        user.setCreateUserId(loginUserId);
        user.setCreateUserName(loginUserName);
        user.setCreateTime(new Date());
        user.setUpdateUserId(loginUserId);
        user.setUpdateUserName(loginUserName);
        user.setUpdateTime(new Date());
        user.setDeletedFlag(GloatDeletedFlagEnum.GLOAT_DELETED_FLAG.getDeletedFlag());
        int insertRow = userMapper.insert(user);
        if(insertRow != 1){
            return ResponseResult.error(ErrorCodeEnum.DATA_UPDATE_FAIL);
        }
        return ResponseResult.success();
    }

    @Override
    public ResponseResult<HashMap<String, Object>> userPage(UserQuery userQuery) {
        Integer pageIndex = userQuery.getPageIndex();
        Integer pageSize = userQuery.getPageSize();
        //获取当前登录用户
        TokenData tokenData = TokenData.takeFormRequest();
        Long userId = tokenData.getUserId();
        String userName = tokenData.getUserName();
        //对总记录数进行缓存,减少查询次数
        String sessionId = "userPage-" + userId + "-" + userName;
        //构建redis的sessionKey,相当于文件夹
        String sessionIdKey = RedisKeyUtil.makeSessionIdKey(sessionId);
        RBucket<Object> countBucket = redissonClient.getBucket(sessionIdKey);
        Object value = countBucket.get();
        long totalCount = 0L;
        if(ObjectUtil.isNotEmpty(value)){
            PageMethod.startPage(pageIndex, pageSize,false);
            totalCount = (Long)value;
        }else {
            PageMethod.startPage(pageIndex, pageSize);
        }
        //构建分页对象
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        if(StrUtil.isNotBlank(userQuery.getUserName())){
        wrapper.like(User::getUserName,userQuery.getUserName());
        }
        if(StrUtil.isNotBlank(userQuery.getMobilePhone())){
            wrapper.eq(User::getMobilePhone,userQuery.getMobilePhone());
        }
        //指定查询字段
        wrapper.select(User::getUserId,User::getUserName,User::getMobilePhone);
        List<User> userList = userMapper.selectList(wrapper);
        List<UserVo> userVoList = MyModelUtil.copyCollectionTo(userList, UserVo.class);
        //获取分页信息
        if(userList instanceof Page && ObjectUtil.isEmpty(value)){
              Page<User> userPage = (Page<User>) userList;
              totalCount = userPage.getTotal();
              //缓存总条数
            countBucket.set(totalCount);
            countBucket.expire(600, TimeUnit.SECONDS);
        }

        Page<UserVo> resultPage = new Page<>();
        if(CollectionUtil.isNotEmpty(userList)) {
            resultPage.addAll(userVoList);
        }
        resultPage.setTotal(totalCount);
        resultPage.setPageNum(pageIndex);
        resultPage.setPageSize(pageSize);
        resultPage.setPages((int)totalCount % pageSize == 0 ? (int)totalCount / pageSize : (int)totalCount / pageSize + 1);
        return ResponseResult.success(MyPageUtil.makePageResponse(resultPage));
    }

    @Override
    public ResponseResult<Void> updateUser(UserDto dto) {
        //属性赋值
        if(dto.getUserId() == null){
            return ResponseResult.error(ErrorCodeEnum.PARAMETER_CANNOT_BE_EMPTY);
        }
            User user = BeanUtil.copyProperties(dto, User.class);
            Long loginUserId = TokenData.takeFormRequest().getUserId();
            String loginUserName = TokenData.takeFormRequest().getUserName();
            user.setUpdateUserId(loginUserId);
            user.setUpdateUserName(loginUserName);
            user.setUpdateTime(new Date());
            int update = userMapper.updateById(user);
            if(update != 1){
                return ResponseResult.error(ErrorCodeEnum.DATA_UPDATE_FAIL);
            }
            return ResponseResult.success();

    }

    @Override
    public ResponseResult<User> userView(Long userId) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUserId,userId);
        User user = userMapper.selectById(userId);
        return ResponseResult.success(user);
    }

    @Override
    public ResponseResult<Void> deleteUser(List<Long> userIdList) {
        if(CollectionUtil.isEmpty(userIdList)){
            return ResponseResult.error(ErrorCodeEnum.PARAMETER_CANNOT_BE_EMPTY);
        }
        int deleteRow = userMapper.deleteBatchIds(userIdList);
        if(deleteRow != userIdList.size()){
            return ResponseResult.error(ErrorCodeEnum.DATA_UPDATE_FAIL);
        }
        return ResponseResult.success();
    }

    @Override
    public void exportUser(UserQuery userQuery, HttpServletResponse response)  {
        List<File> timeFiles = new ArrayList<>();
        //使用多线程处理批量导出百万数据
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);

        String encodedFileName;
        // 计算文件名编码放到循环外，避免重复编码
        try {
            encodedFileName = URLEncoder.encode("用户数据", "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        try {
            //创建CompletionService 来跟踪任务完成状态
            CompletionService<File> completionService = new ExecutorCompletionService<>(executorService);
            for (int pageNum = 0; pageNum < TOTAL_RECORDS / BATCH_SIZE; pageNum++) {
                final int currentPageNum = pageNum;
                completionService.submit(() -> {

                        //每个线程要执行的任务
                        //先查询十万条数据
                        List<User> userList = findOneHundredThousandData(userQuery,currentPageNum);
                    System.out.println("查出来的数据大小:{}" + userList.size());
                        if (CollectionUtils.isEmpty(userList)) {
                            return null;
                        }
                        long exportStartTime = System.currentTimeMillis();
                        File file = null;
                    try {
                        //调用EasyExcel工具类导出
                        // 确保 response 是线程安全的，或者为每个线程生成独立的导出文件
                        file = ExcelExportUtil.export(userList);
                    } catch (IOException e) {
                        log.error("用户列表线程导出异常,异常信息:{}", e);
                    }
                        long exportEndTime = System.currentTimeMillis();
                        long exportRunTime = exportEndTime - exportStartTime;
                        System.out.println("导出第" + currentPageNum + "十万条数据所花费时间: {} " + exportRunTime / 1000 + "秒");

                   return file;
                });
            }
            //等待所有线程任务完成,并将文件合并
            for (int i = 0; i < TOTAL_RECORDS / BATCH_SIZE; i++) {
                Future<File> future = completionService.take();//等待完成
                File timeFile = future.get();
                if(timeFile != null){
                    timeFiles.add(timeFile);
                }
            }

            //合并文件并写入到响应
            margeAndWriteToResonse(timeFiles,response,encodedFileName);
        }catch (Exception e){
            log.error("导出异常: {} ", e);
        }finally {
            //线程池关闭
            executorService.shutdown();
        }
    }

    /**
     * 合并临时文件并写入响应
     * @param timeFiles
     * @param response
     * @param encodedFileName
     */
    private void margeAndWriteToResonse(List<File> timeFiles, HttpServletResponse response, String encodedFileName) throws IOException {
        //设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition","attachment;filename=" + encodedFileName);

        try (ServletOutputStream outputStream = response.getOutputStream()){
            for (File file : timeFiles) {
                Files.copy(file.toPath(),outputStream);
            }
        }
    }

    private List<User> findOneHundredThousandData(UserQuery userQuery,Integer currentPageNum){

//        while (true) {
        //先查询当前页数据
        Integer pageIndex = userQuery.getPageIndex();
        Integer pageSize = userQuery.getPageSize();
        //每次导出十万条数据
        pageIndex = currentPageNum; //当前页
        pageSize = BATCH_SIZE; //分页大小
        PageMethod.startPage(pageIndex, pageSize,false); //不执行总条数查询
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(userQuery.getUserName())) {
            wrapper.like(User::getUserName, userQuery.getUserName());
        }
        if (StrUtil.isNotBlank(userQuery.getMobilePhone())) {
            wrapper.eq(User::getMobilePhone, userQuery.getMobilePhone());
        }
        long selectStartTime = System.currentTimeMillis();
        List<User> userList = userMapper.selectList(wrapper);
        long selectEndTime = System.currentTimeMillis();
        long selectRunTime = selectEndTime - selectStartTime;
        System.out.println("查询十万条数据所花费时间: {} " + selectRunTime / 1000 + "秒");
        return userList;
    }
}
